{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Property Decorators"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As I explain in the lecture video, the `property` callable actually returns itself:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = property(fget=lambda self: print('getting property'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<property at 0x7f8348671778>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see `p` is a property, and in fact is the same property that was created."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Think back to how decorators work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_decorator(fn):\n",
    "    print('decorating function')\n",
    "    def inner(*args, **kwargs):\n",
    "        print('running decorated function')\n",
    "        return fn(*args, **kwargs)\n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def undecorated_function(a, b):\n",
    "    print('running original function')\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can decorate our undecorated function this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decorating function\n"
     ]
    }
   ],
   "source": [
    "decorated_func = my_decorator(undecorated_function)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can call the decorated function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "running decorated function\n",
      "running original function\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "30"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "decorated_func(10, 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now instead of giving the decorate function a new symbol, we could have just re-used the same symbol:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decorating function\n"
     ]
    }
   ],
   "source": [
    "def my_func(a, b):\n",
    "    print('running original function')\n",
    "    return a + b\n",
    "\n",
    "my_func = my_decorator(my_func)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "running decorated function\n",
      "running original function\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "30"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func(10, 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And of course this is exactly what the decorator `@` syntax does:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decorating function\n"
     ]
    }
   ],
   "source": [
    "@my_decorator\n",
    "def my_func(a, b):\n",
    "    print('running original function')\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "running decorated function\n",
      "running original function\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "30"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func(10, 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ok, now that we've refreshed our memory on decorators, we should be ready to look at the `property` callable.\n",
    "\n",
    "The `property` callable creates a property object, **and returns it**.\n",
    "\n",
    "In other words, we could create our property this way, as usual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    name = property(name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = Person('Alex')\n",
    "\n",
    "p.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But you'll notice that line: `name = property(name)` - that's exactly what the decorator syntax does for us!\n",
    "\n",
    "So instead we can write:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    @property\n",
    "    def name(self):\n",
    "        return self._name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Guido'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = Person('Guido')\n",
    "p.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you refresh your memory on the single dispatch generic function decorator, you'll remember that the decorated function included another property, the `register` property that was itself a decorator.\n",
    "\n",
    "Well, the `property` object has some properties, like `setter` that will basically accept a reference to the setter method, and return itself also."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = property(lambda self: 'getter')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['__class__',\n",
       " '__delattr__',\n",
       " '__delete__',\n",
       " '__dir__',\n",
       " '__doc__',\n",
       " '__eq__',\n",
       " '__format__',\n",
       " '__ge__',\n",
       " '__get__',\n",
       " '__getattribute__',\n",
       " '__gt__',\n",
       " '__hash__',\n",
       " '__init__',\n",
       " '__init_subclass__',\n",
       " '__isabstractmethod__',\n",
       " '__le__',\n",
       " '__lt__',\n",
       " '__ne__',\n",
       " '__new__',\n",
       " '__reduce__',\n",
       " '__reduce_ex__',\n",
       " '__repr__',\n",
       " '__set__',\n",
       " '__setattr__',\n",
       " '__sizeof__',\n",
       " '__str__',\n",
       " '__subclasshook__',\n",
       " 'deleter',\n",
       " 'fdel',\n",
       " 'fget',\n",
       " 'fset',\n",
       " 'getter',\n",
       " 'setter']"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, we can \"register\" and setter method, using the `setter` callable, and get our property back as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<property at 0x7f83581afe58>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "p2 = p.setter(lambda self: 'setter')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(140202095607384, 140202095618152)"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(p), id(p2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you'll notice that the property id has changed. The setter callable actually creates a new property (with both the original getter, and the new setter assigned).\n",
    "\n",
    "But that does not really matter, we just have a new property object that we can use to assign to a symbol - and that property will have both the getter and the setter defined.\n",
    "\n",
    "Let's do this manually (without the decorator syntax first):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    name = property(name)\n",
    "    \n",
    "    # creating another symbol that holds on to \n",
    "    # the name property\n",
    "    name_prop = name \n",
    "    \n",
    "    # because herte I'm redefining name, so we lose \n",
    "    # our original reference to the property object\n",
    "    def name(self, value):\n",
    "        self._name = value\n",
    "        \n",
    "    name = name_prop.setter(name)\n",
    "    \n",
    "    # finally delete name_prop which we no longer need\n",
    "    del name_prop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__init__': <function __main__.Person.__init__(self, name)>,\n",
       "              'name': <property at 0x7f83581b2bd8>,\n",
       "              '__dict__': <attribute '__dict__' of 'Person' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Person' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Person.__dict__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we now have a `name` property that we created in two steps: first create the property with just a getter.\n",
    "\n",
    "Then we replaced our property with a new property that had both the getter and the setter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = Person('Alex')\n",
    "p.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Raymond'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name = 'Raymond'\n",
    "p.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hopefully you can now see where the original property (with just the getter), had a callable `setter` that \"added\" the setter to the property (by creating a new property with both getter and setter), that also returned the (new) property object.\n",
    "\n",
    "So, we can simplify our code this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    @property\n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    # what's the property name now? --> name\n",
    "    # so name has a setter callable\n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that if we had not named our setter function `name` the property name would have changed!\n",
    "\n",
    "Remember that:\n",
    "```\n",
    "@dec\n",
    "def my_func():\n",
    "    pass\n",
    " ```\n",
    " returns a decorated function with the same name as the original function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__init__': <function __main__.Person.__init__(self, name)>,\n",
       "              'name': <property at 0x7f83581c46d8>,\n",
       "              '__dict__': <attribute '__dict__' of 'Person' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Person' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Person.__dict__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Guido'"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name = 'Guido'\n",
    "p.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just to show you, if we had not used the same name for the setter function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    @property\n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    # property is now called name\n",
    "    \n",
    "    @name.setter\n",
    "    def full_name(self, value):\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__init__': <function __main__.Person.__init__(self, name)>,\n",
       "              'name': <property at 0x7f83581c4db8>,\n",
       "              'full_name': <property at 0x7f83581c4f48>,\n",
       "              '__dict__': <attribute '__dict__' of 'Person' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Person' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Person.__dict__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see we now have two properties on the class! The first one `name` will only work as a getter. And the second one `full_name` will work as both a getter and a setter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.full_name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.full_name = 'Raymond'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Raymond'"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.full_name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But this won't work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "can't set attribute\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    p.name = 'Guido'\n",
    "except AttributeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Technically, the property callable has both a getter and setter method - so we can create the setter first, then \"add in\" the getter. But since the first argument to `property` is the getter, we have to work a bit more to do it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    name = property()  # an \"empty\" prroperty - no getter or setter\n",
    "    \n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By the way, we now have a property that can be set, but not read back!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'_name': 'Alex'}"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.__dict__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.name = 'Raymond'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'_name': 'Raymond'}"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.__dict__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unreadable attribute\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    p.name\n",
    "except AttributeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, if you ever need an attribute that is \"write-only\" - you can do it. Maybe the data is sensitive, and you want to set it, but not show back to users... But the data is never truly private, so at best you're obfuscating the data - so in my experience I've never had to do something like that. Just wanted you to see this in case the need ever came up."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But let's finish this up and make the property read/write:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    name = property()  # an \"empty\" prroperty - no getter or setter\n",
    "    \n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        self._name = value\n",
    "        \n",
    "    @name.getter\n",
    "    def name(self):\n",
    "        return self._name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex'"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.name = 'Raymond'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Raymond'"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The deleter works the same way, and we'll come back to it soon."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lastly you'll recall that we could set up a docstring when using the `property` callable.\n",
    "\n",
    "The standard technique is to simply define the docstring in the getter function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    @property\n",
    "    def name(self):\n",
    "        \"\"\"The Person's name.\"\"\"\n",
    "        return self._name\n",
    "    \n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on property:\n",
      "\n",
      "    The Person's name.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Person.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Person in module __main__:\n",
      "\n",
      "class Person(builtins.object)\n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, name)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  name\n",
      " |      The Person's name.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happens if we set it in the setter instead?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    @property\n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        \"\"\"The Person's name.\"\"\"\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on property:\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Person.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Person in module __main__:\n",
      "\n",
      "class Person(builtins.object)\n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, name)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  name\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the property docstring is only set on the getter. So how to set a docstring with a write-only property? We can do that when we create the initial property:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self._name = name\n",
    "        \n",
    "    name = property(doc='Write-only name property.')\n",
    "    \n",
    "    @name.setter\n",
    "    def name(self, value):\n",
    "        self._name = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on property:\n",
      "\n",
      "    Write-only name property.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Person.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
